package com.ksd.pug.config.interceptor.jwt;

import com.auth0.jwt.interfaces.DecodedJWT;
import com.ksd.pug.commons.enums.ResultStatusEnumA;
import com.ksd.pug.commons.ex.PugBussinessException;
import com.ksd.pug.commons.threadlocal.UserThrealLocal;
import com.ksd.pug.commons.utils.fn.asserts.Vsserts;
import com.ksd.pug.commons.utils.json.FastJsonUtil;
import com.ksd.pug.config.anno.LoginCheck;
import com.ksd.pug.config.cache.EHCacheService;
import com.ksd.pug.pojo.SysLoginUser;
import com.ksd.pug.service.jwt.IJwtBlackService;
import com.ksd.pug.service.user.ISysLoginUserService;
import com.ksd.pug.utils.jwt.JwtTokenUtils;
import com.ksd.pug.utils.redis.PugRedisOperator;
import com.pug.redis.config.PugRedisCacheTemplate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.TimeUnit;

/**
 * 用户token认证
 * @date 2022/1/6 17:48
 */
@Slf4j
@Component
public class JwtInterceptor implements HandlerInterceptor {

    @Autowired
    private ISysLoginUserService sysLoginUserService;
    @Autowired
    private PugRedisCacheTemplate pugRedisCacheTemplate;
    @Autowired
    @Qualifier("jwtBlackStringService")
    IJwtBlackService jwtBlackService;

    @Autowired
    private PugRedisOperator redisOperator;
    @Autowired
    private EHCacheService ehCacheService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            // 让方法的优先级更高
            // 获取方法上面是否存在LoginCheck注解，如果存在并值是false.当然默认值也是false
            LoginCheck methodAnnotation = handlerMethod.getMethodAnnotation(LoginCheck.class);
            // methodAnnotation!=null 代表你加了注解,如果没加就不进入，就说明验证
            // !methodAnnotation.value() 加了如果是false，说明不需要验证了，就直接返回
            // 一句话：在开发中你要么不要加，要么加上去就使用默认值
            if (methodAnnotation!=null && !methodAnnotation.value()) {
                String authUserId = request.getHeader("AuthUserId");
                SysLoginUser user = (SysLoginUser)ehCacheService.getCache("xq:login:user:" + authUserId);
                if(Vsserts.isNull(user)) {
                    String userCacheJson = redisOperator.get("xq:login:user:" + authUserId);
                    user = FastJsonUtil.toBean(userCacheJson, SysLoginUser.class);
                }
                log.info("把用户放入线程{}",user);
                if(Vsserts.isNotNull(user)){
                    UserThrealLocal.put(user);
                }
                return true;
            }

            // 然后就是类的优先级
            Class<?> beanType = handlerMethod.getBeanType();
            LoginCheck annotation = beanType.getAnnotation(LoginCheck.class);
            if (annotation!=null && !annotation.value()) {
                String authUserId = request.getHeader("AuthUserId");
                SysLoginUser user = (SysLoginUser)ehCacheService.getCache("xq:login:user:" + authUserId);
                if(Vsserts.isNull(user)) {
                    String userCacheJson = redisOperator.get("xq:login:user:" + authUserId);
                    user = FastJsonUtil.toBean(userCacheJson, SysLoginUser.class);
                }
                log.info("把用户放入线程{}",user);
                if(Vsserts.isNotNull(user)){
                    UserThrealLocal.put(user);
                }
                return true;
            }
        }

        // 1: 判断当前请求头中是否有token
        String token = JwtTokenUtils.getJwtToken(request);
        log.info("1:判断当前请求头中是否有token:{}", token);
        if (Vsserts.isEmpty(token)) {
            throw new PugBussinessException(ResultStatusEnumA.TOKEN_EMPTY);
        }

        // 2:判断当前token是不是黑名单里
        if (jwtBlackService.isBlackList(token)) {
            throw new PugBussinessException(ResultStatusEnumA.TOKEN_EMPTY_EXPIRED);
        }

        // 3: 校验token 是否过期 isverfiy = false 代表过期
        boolean isverfiy = JwtTokenUtils.isverfiy(token);
        DecodedJWT jwtFromToken = JwtTokenUtils.getJWTFromToken(token);
        String tokenKey = "sys:user:token:";
        if (jwtFromToken != null) {
            String userId = jwtFromToken.getSubject();
            tokenKey = "sys:user:token:" + userId;
            if (!isverfiy) {
                // 4：如果你过期了就从缓存中去获取token
                token = pugRedisCacheTemplate.getCacheObject(tokenKey);
            }
        }
        // 5：建议使用这种方式,这种具有实时性  5==== status= 1
        String userId = com.ksd.pug.utils.jwt.JwtTokenUtils.parseToken(token);
        if (Vsserts.isNull(userId) || Vsserts.isNull(pugRedisCacheTemplate.getCacheObject(tokenKey))) {
            throw new PugBussinessException(ResultStatusEnumA.TOKEN_UN_VALID);
        }

        SysLoginUser user = sysLoginUserService.getById(Long.parseLong(userId));
        // 6：判断用户是否激活
        log.info("2:判断用户是否激活");
        if (user.getStatus().equals(0)) {
            throw new PugBussinessException(ResultStatusEnumA.STATUS_UN_VALID);
        }
        //判断用户是否删除
        log.info("3:判断用户是否删除");
        if (user.getIsdelete().equals(1)) {
            throw new PugBussinessException(ResultStatusEnumA.DELETE_UN_VALID);
        }

        // 7：判断是否需要续期 30分钟就续期
        if (pugRedisCacheTemplate.getExpireTime(tokenKey) < 60 * 40) {
            String newToken = JwtTokenUtils.createToken(user);
            // 缓存也进行同步一下缓存
            pugRedisCacheTemplate.setCacheObject("sys:user:permission:list:"+token, sysLoginUserService.findBySysPermissionUserId(user.getId()),JwtTokenUtils.TOKEN_EXPIRATION, TimeUnit.MILLISECONDS);
            pugRedisCacheTemplate.setCacheObject(tokenKey, newToken, JwtTokenUtils.REDIS_TOKEN_EXPIRATION, TimeUnit.MILLISECONDS);
        }
        // 放入本地线程中
        UserThrealLocal.put(user);

        return true;
    }


    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        UserThrealLocal.remove();
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        UserThrealLocal.remove();
    }
}
